package group.rober.saber.jdbc.accessor.impl;

import groovy.lang.GroovyObject;
import group.rober.saber.jdbc.DefaultNameConverter;
import group.rober.saber.jdbc.NameConverter;
import group.rober.saber.jdbc.accessor.listener.DeleteListener;
import group.rober.saber.jdbc.accessor.listener.InsertListener;
import group.rober.saber.jdbc.accessor.listener.UpdateListener;
import group.rober.saber.jdbc.exception.OptimisticLockException;
import group.rober.saber.kit.*;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;

import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.lang.reflect.Field;
import java.util.*;

/**
 * JavaBean数据读写
 * Created by tisir<yangsong158@qq.com> on 2017-06-02
 */
public class BeanDataAccessor {
    private String table;
    private NameConverter nameConverter = new DefaultNameConverter();
    private NamedParameterJdbcTemplate jdbcTemplate;
    private boolean blankStringReplacedByNull = true;
    private String optimisticLockProperty = null; //乐观锁属性

    public String getTable() {
        return table;
    }

    public void setTable(String table) {
        this.table = table;
    }

    public NameConverter getNameConverter() {
        return nameConverter;
    }

    public void setNameConverter(NameConverter nameConverter) {
        this.nameConverter = nameConverter;
    }

    public NamedParameterJdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    public void setJdbcTemplate(NamedParameterJdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public boolean isBlankStringReplacedByNull() {
        return blankStringReplacedByNull;
    }

    public void setBlankStringReplacedByNull(boolean blankStringReplacedByNull) {
        this.blankStringReplacedByNull = blankStringReplacedByNull;
    }

    public String getOptimisticLockProperty() {
        return optimisticLockProperty;
    }

    public void setOptimisticLockProperty(String optimisticLockProperty) {
        this.optimisticLockProperty = optimisticLockProperty;
    }

    public <T> List<T> selectList(Class<T> classType,String sql, Map<String,?> parameters){
        return jdbcTemplate.query(sql, parameters, new BeanPropertyRowMapper<T>(classType));
//        try{
//            return jdbcTemplate.query(sql, parameters, new BeanPropertyRowMapper<T>(classType));
//        }catch(DataAccessException e){
//            return new ArrayList<T>(0);
//        }
    }

    public <T> int insert(T entity){
        return insert(entity,null);
    }

    public <T> int insert(T entity, InsertListener<T> listener) {
        if(listener != null) listener.before(entity);

        TableRow tableRow = parseBeanToTableMeta(entity);
        KeyHolder keyholder=new GeneratedKeyHolder();

        AccessorSQLKit.blankProcess(blankStringReplacedByNull,tableRow.dataRow);
        String sql = AccessorSQLKit.genInsertSql(tableRow.table,tableRow.dataRow,optimisticLockProperty,nameConverter);
        SqlParameterSource ps=new MapSqlParameterSource(tableRow.dataRow);

        //execute
        int r = jdbcTemplate.update(sql,ps,keyholder);

        if(listener != null) listener.after(entity);
        return r;
    }

    public <T> int update(T entity){
        return update(entity,null);
    }

    public <T> int update(T entity,UpdateListener<T> listener) {
        if(listener != null) listener.before(entity);

        TableRow tableRow = parseBeanToTableMeta(entity);

        AccessorSQLKit.blankProcess(blankStringReplacedByNull,tableRow.dataRow);
        String sql = AccessorSQLKit.genUpdateSql(tableRow.table,tableRow.dataRow,tableRow.keyRow,optimisticLockProperty,nameConverter);
        MapSqlParameterSource ps=new MapSqlParameterSource(tableRow.dataRow);
        ps.addValues(tableRow.keyRow);

        //EXEC
        int r = jdbcTemplate.update(sql,ps);
        if(!StringKit.isBlank(optimisticLockProperty)&&r==0){
            throw new OptimisticLockException("SQL[{0}],Parameter:{1}",sql, JSONKit.toJsonString(entity));
        }

        if(listener != null) listener.after(entity);
        return r;
    }

    public <T> int save(T entity){
        return save(entity,null,null);
    }
    public <T> int save(T entity, InsertListener<T> insertListener, UpdateListener<T> updateListener) {
        TableRow tableRow = parseBeanToTableMeta(entity);

        boolean exists = AccessorSQLKit.exists(jdbcTemplate,tableRow.table,tableRow.keyRow,nameConverter);
        if(exists){
            return update(entity,updateListener);
        }else{
            return insert(entity,insertListener);
        }
    }

    public <T> int delete(T entity){
        return delete(entity,null);
    }

    public <T> int delete(T entity, DeleteListener<T> listener) {
        if(listener != null)listener.before(entity);

        TableRow tableRow = parseBeanToTableMeta(entity);
        StringBuilder sql = new StringBuilder();
        sql.append(" DELETE FROM ");
        sql.append(tableRow.table);
        sql.append(" WHERE ");
        sql.append(AccessorSQLKit.genWhereClause(tableRow.keyRow,nameConverter));

        int r = jdbcTemplate.update(sql.toString(),tableRow.keyRow);

        if(listener != null)listener.after(entity);
        return r;
    }

    public <T> int insert(List<T> entityList){
        return insert(entityList,null);
    }
    public <T> int insert(List<T> entityList,InsertListener<List<T>> listener) {
        ValidateKit.notEmpty(entityList,"insert data list cannot be empty");

        //数据插入之前
        if(listener != null) listener.before(entityList);

        //数据模型转换
        TableRows tableRows = parseBeanToMultiTableMeta(entityList);
        int ret = 0;
        String sql = null;
        SqlParameterSource[] batchArgs = new SqlParameterSource[entityList.size()];
        for(int i=0;i<entityList.size() ;i++){
            T entity = entityList.get(i);
            TableRow tableRow = parseBeanToTableMeta(entity);
            if(sql==null){
                sql = AccessorSQLKit.genInsertSql(tableRows.table,tableRow.dataRow,optimisticLockProperty,nameConverter);
            }
            //空串值处理成NULL
            AccessorSQLKit.blankProcess(blankStringReplacedByNull,tableRow.dataRow);
            batchArgs[i]=new MapSqlParameterSource(tableRow.dataRow);
        }
        //执行插入
        int[] r = jdbcTemplate.batchUpdate(sql,batchArgs);
        for(int i=0;i<r.length;i++){
            ret += r[i];
        }

        //插入之后
        if(listener != null) listener.after(entityList);
        return ret;

    }

    public <T> int update(List<T> entityList){
        return update(entityList,null);
    }

    public <T> int update(List<T> entityList,UpdateListener<List<T>> listener) {
        ValidateKit.notEmpty(entityList,"update data list cannot be empty");
        if(listener != null) listener.before(entityList);

//        TableRows tableRows = parseBeanToMultiTableMeta(entityList);
        parseBeanToMultiTableMeta(entityList);

        int ret = 0;
        String sql = null;
        SqlParameterSource[] batchArgs = new SqlParameterSource[entityList.size()];
        for(int i=0;i<entityList.size() ;i++){
            T entity = entityList.get(i);
            TableRow tableRow = parseBeanToTableMeta(entity);
            if(sql==null){
                sql = AccessorSQLKit.genUpdateSql(tableRow.table,tableRow.dataRow,tableRow.keyRow,optimisticLockProperty,nameConverter);
            }
            //空串值处理成NULL
            AccessorSQLKit.blankProcess(blankStringReplacedByNull,tableRow.dataRow);
            batchArgs[i]=new MapSqlParameterSource(tableRow.dataRow);
        }

        //EXEC
        int[] r = jdbcTemplate.batchUpdate(sql,batchArgs);
        for(int i=0;i<r.length;i++){
            ret += r[i];
            if(r[i]==0){
                throw new OptimisticLockException("SQL[{0}],Parameter:{1}",sql, JSONKit.toJsonString(entityList.get(i)));
            }
        }

        if(listener != null) listener.after(entityList);

        return ret;
    }

    public <T> int save(List<T> entityList){
        return save(entityList,null,null);
    }

    public <T> int save(List<T> entityList,InsertListener<List<T>> insertListener,UpdateListener<List<T>> updateListener) {
        ValidateKit.notEmpty(entityList,"save data list cannot be empty");
        TableRows tableRows = parseBeanToMultiTableMeta(entityList);

        List<Map<String, Object>> keyAttributes = tableRows.keyRows;

        //根据主键,计算出存在的数据,这部分数据只能做UPDATE
        List<Integer> updatedIndexList = new ArrayList<Integer>();
        for(int i=0;i<keyAttributes.size();i++){
            boolean exists = AccessorSQLKit.exists(jdbcTemplate,tableRows.table,keyAttributes.get(i),nameConverter);
            if(exists)updatedIndexList.add(i);
        }

        //把需要插入的部分和更新的部分分出来
        List<T> insertDataList = new ArrayList<T>();
        List<T> updateDataList = new ArrayList<T>();
        for(int i=0;i<entityList.size();i++){
            if(updatedIndexList.indexOf(i)>=0){
                updateDataList.add(entityList.get(i));
            }else{
                insertDataList.add(entityList.get(i));
            }
        }

        //分别做插入和更新操作
        int ret = 0;
        if(insertDataList.size()>0){
            ret += insert(insertDataList,insertListener);
        }
        if(updateDataList.size()>0){
            ret += update(updateDataList,updateListener);
        }

        return ret;
    }

    public <T> int delete(List<T> entityList){
        return delete(entityList,null);
    }
    public <T> int delete(List<T> entityList,DeleteListener<List<T>> listener) {
        if(listener != null)listener.before(entityList);

        ValidateKit.notEmpty(entityList,"delete data list cannot be empty");
        TableRows tableRows = parseBeanToMultiTableMeta(entityList);
        List<Map<String, Object>> keyAttributes = tableRows.keyRows;

        int ret = 0;
        StringBuilder sql = new StringBuilder();
        SqlParameterSource[] batchArgs = new SqlParameterSource[keyAttributes.size()];
        for(int i=0;i<keyAttributes.size() ;i++){
            if(sql.length()==0){
                sql.append(" DELETE FROM ");
                sql.append(tableRows.table);
                sql.append(" WHERE ");
                sql.append(AccessorSQLKit.genWhereClause(keyAttributes.get(i),nameConverter));
            }
            batchArgs[i]=new MapSqlParameterSource(keyAttributes.get(i));
        }

        int[] r = jdbcTemplate.batchUpdate(sql.toString(),batchArgs);
        for(int i=0;i<r.length;i++){
            ret += r[i];
        }
        if(listener != null)listener.after(entityList);
        return ret;
    }

    protected <T> TableRows parseBeanToMultiTableMeta(List<T> objectList){
        TableRows tableRows = new TableRows();
        for(T object : objectList){
            TableRow m = parseBeanToTableMeta(object);
            tableRows.table = m.table;
            tableRows.dataRows.add(m.dataRow);
            tableRows.keyRows.add(m.keyRow);
        }
        return tableRows;
    }

    protected TableRow parseBeanToTableMeta(Object object){
        ValidateKit.notNull(object);

        TableRow tableRow = new TableRow();

        if(!StringKit.isBlank(this.table)){
            tableRow.table = this.table;
        }else{
            Table tableAnno = object.getClass().getAnnotation(Table.class);
            if(tableAnno==null){
                tableRow.table = nameConverter.getTableName(object.getClass());
            }else{
                tableRow.table = tableAnno.name();
            }
        }
        tableRow.dataRow = BeanKit.bean2Map(object);

//        tableRow.isGroovyObject = (object instanceof GroovyObject);
//        Field[] fields = object.getClass().getFields();
        Field[] fields = object.getClass().getDeclaredFields();
        for(Field field : fields){
            String fieldName = field.getName();
            Object fieldValue = tableRow.dataRow.get(fieldName);
            String actualFieldName = fieldName;
            Id idAnno = field.getAnnotation(Id.class);
            Column columnAnno = field.getAnnotation(Column.class);

            //如果属性上注解了属性对应的数据表字段,则这里需要重新处理下
            if(columnAnno!=null){
                String columnName = columnAnno.name();
                String propertyName = nameConverter.getPropertyName(columnName);
                if(!propertyName.equals(fieldName)){
                    actualFieldName = fieldName;
                    tableRow.dataRow.remove(fieldName);
                    tableRow.dataRow.put(propertyName,fieldValue);
                }
            }
            //不持久化的字段,不要记录
            Transient transientAnno = field.getAnnotation(Transient.class);
            if(transientAnno != null){
                tableRow.dataRow.remove(fieldName);
                continue;//不需要持久化的字段,忽略
            }
//            if("metaClass".equals(fieldName)){
//                continue;
//            }
//            if("META_CLASS".equals(actualFieldName)){
//                continue;
//            }

            if(idAnno != null){
                tableRow.keyRow.put(actualFieldName,fieldValue);
            }
        }

        return tableRow;
    }



    protected static class TableRow {
        String table;
//        boolean isGroovyObject;
        Map<String,Object> dataRow = MapKit.newLinkedHashMap();
        Map<String,Object> keyRow = MapKit.newLinkedHashMap();
    }

    protected static class TableRows {
        String table;
//        boolean isGroovyObject;
        List<Map<String,Object>> dataRows = new ArrayList<Map<String,Object>>();
        List<Map<String,Object>> keyRows = new ArrayList<Map<String,Object>>();
    }
}
